﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Collections;

namespace LeanEngine.Data.Linq
{
    internal sealed class DatabaseQuery<T> : IOrderedQueryable<T>, IQueryable<T>, IQueryProvider, IEnumerable<T>, IOrderedQueryable, IQueryable, IEnumerable
    {
        private readonly DatabaseContext context;
        private readonly Expression queryExpression;

        public DatabaseQuery(DatabaseContext context, Expression expression)
        {
            this.context = context;
            queryExpression = expression;
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return ((IEnumerable<T>)context.Provider.Execute(queryExpression)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)context.Provider.Execute(queryExpression)).GetEnumerator();
        }

        IQueryable<TResult> IQueryProvider.CreateQuery<TResult>(Expression expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (!typeof(IQueryable<TResult>).IsAssignableFrom(expression.Type))
                throw new ArgumentException("ExpectedQueryableArgument");

            return new DatabaseQuery<TResult>(context, expression);
        }

        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            Type elementType = expression.Type.GetElementType();
            Type type = typeof(IQueryable<>).MakeGenericType(new[] { elementType });
            if (!type.IsAssignableFrom(expression.Type))
                throw new ArgumentException("ExpectedQueryableArgument");

            return (IQueryable)Activator.CreateInstance(
              typeof(DatabaseQuery<>).MakeGenericType(new[] { elementType }),
              new object[] { context, expression });
        }

        object IQueryProvider.Execute(Expression expression)
        {
            return context.Provider.Execute(expression);
        }

        TElement IQueryProvider.Execute<TElement>(Expression expression)
        {
            var resultValue = context.Provider.Execute(expression);
            var returnValue = (TElement)Convert.ChangeType(resultValue, typeof(TElement));
            return returnValue;
        }

        public override string ToString()
        {
            return context.Provider.GetQueryText(this.queryExpression);
        }

        Type IQueryable.ElementType
        {
            get { return typeof(T); }
        }

        Expression IQueryable.Expression
        {
            get { return queryExpression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return this; }
        }
    }
}